package com.hfzy.ihk.common.core.elasticsearch.repository.base;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hfzy.ihk.common.core.elasticsearch.ElasticsearchScripts;
import com.hfzy.ihk.common.facade.BaseEntity;
import com.hfzy.ihk.common.util.Utils;
import com.hfzy.ihk.common.util.string.StringTools;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.persistence.*;

/**
 * Created by Administrator on 2018/3/19.
 *
 * @author wws
 */
@Component
@ConditionalOnBean(ElasticsearchTemplate.class)
public class CustomizedRepositoryImpl<T,ID extends Serializable> implements CustomizedRepository<T,ID>{

    private static final Logger logger = LoggerFactory.getLogger(CustomizedRepositoryImpl.class);

    @Autowired
    ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    ElasticsearchConverter elasticsearchConverter;

    private static ObjectMapper mapper = new ObjectMapper();

    public CustomizedRepositoryImpl() {
       /* this.entityClass = getEntityClass();

        try {
            Document d = entityClass.getAnnotation(Document.class);
            index = d.indexName();
            type = d.type();
        } catch (Exception e) {
            logger.error("异常：",e);
        }*/


    }

    public ElasticsearchPersistentEntity getPersistentEntityFor(Class clazz) {
        Assert.isTrue(clazz.isAnnotationPresent(Document.class), "Unable to identify index name. " + clazz.getSimpleName()
                + " is not a Document. Make sure the document class is annotated with @Document(indexName=\"foo\")");
        return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
    }

    /**
     * 添加一条数据到文档的List字段
     * 只要版本号@version字段相应增加就行
     * 不存在冲突问题
     * @param paramsValue 这个值注意要和表字段的类型匹配，不然报错
     *                    只支持这么多种类型：
     *                      type = 基础类型
                            type = List.class;
                            type = Object[].class;
                            type = Map.class;
                            type = ReadableInstant.class;
                            type = BytesReference.class;
                            type = value.getClass();

        对象类型：使用 JSON.toJSON(paramsValue)

     * @see JSON
     * */
    /**------------------不再自动维护version字段-----详细看脚本--------------------*/
    @Override
    public void upsertEntityListFieldByScript(String dataId,
                                              Class entityClass,
                                              String updateFieldName,
                                              Object paramsValue) {

        ElasticsearchPersistentEntity elasticsearchPersistentEntity = getPersistentEntityFor(entityClass);
        String indexName = elasticsearchPersistentEntity.getIndexName();
        String table = elasticsearchPersistentEntity.getIndexType();

        Map<String,Object> map = new HashMap<>();
        //传入需要修改的<字段：值>
        map.put(ElasticsearchScripts.paramValueField, JSON.toJSON(paramsValue));

        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.script(new Script( ScriptType.INLINE,
                "painless",
                /**
                 * id：是指放在服务端的脚本id
                 * code: 脚本
                 * */
                ElasticsearchScripts.createAddToListTemplate(updateFieldName),
                map));

        UpdateQuery updateQuery = new UpdateQuery();
        updateQuery.setIndexName(indexName);
        updateQuery.setType(table);
        updateQuery.setId(dataId);
        updateQuery.setClazz(entityClass);
        updateQuery.setUpdateRequest(updateRequest);
        elasticsearchTemplate.update(updateQuery);
        elasticsearchTemplate.refresh(indexName);
    }

    @Override
    public void upsertEntityListFieldByScript(String dataId,
                                              Class entityClass,
                                              String updateFieldName,
                                              Object paramsValue,String versionFieldName,Long versionValue) {

        ElasticsearchPersistentEntity elasticsearchPersistentEntity = getPersistentEntityFor(entityClass);
        String indexName = elasticsearchPersistentEntity.getIndexName();
        String table = elasticsearchPersistentEntity.getIndexType();

        Map<String,Object> map = new HashMap<>();
        //传入需要修改的<字段：值>
        map.put(ElasticsearchScripts.paramValueField, JSON.toJSON(paramsValue));
        map.put(ElasticsearchScripts.paramVersionValueField, versionValue);

        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.script(new Script( ScriptType.INLINE,
                "painless",
                /**
                 * id：是指放在服务端的脚本id
                 * code: 脚本
                 * */
                ElasticsearchScripts.createAddToListTemplateOld(updateFieldName,versionFieldName),
                map));

        UpdateQuery updateQuery = new UpdateQuery();
        updateQuery.setIndexName(indexName);
        updateQuery.setType(table);
        updateQuery.setId(dataId);
        updateQuery.setClazz(entityClass);
        updateQuery.setUpdateRequest(updateRequest);
        elasticsearchTemplate.update(updateQuery);
        elasticsearchTemplate.refresh(indexName);
    }

    /**
     * 覆盖更新
     * 注：
     * 1、无法更新内部字段是对象的字段
     * 2、更新的字段是List的话，如果原先有类容，要带上，否则会丢失
     * */
    @Override
    public void updateSomeFields(String dataId,
                                 Class entityClass,
                                 Map<String,Object> fieldValueMap){

        ElasticsearchPersistentEntity elasticsearchPersistentEntity = getPersistentEntityFor(entityClass);
        String indexName = elasticsearchPersistentEntity.getIndexName();
        String table = elasticsearchPersistentEntity.getIndexType();

        UpdateRequest updateRequest = new UpdateRequest();

        XContentBuilder xContentBuilder = null;
        try {
            xContentBuilder = contentBuilder(fieldValueMap);
        } catch (Exception e) {
            throw new RuntimeException("updateSomeFields构建数据异常："+e.getMessage());
        }

        updateRequest.doc(xContentBuilder);

        UpdateQuery updateQuery = new UpdateQuery();
        updateQuery.setIndexName(indexName);
        updateQuery.setType(table);
        updateQuery.setId(dataId);
        updateQuery.setClazz(entityClass);
        updateQuery.setUpdateRequest(updateRequest);
        elasticsearchTemplate.update(updateQuery);
        elasticsearchTemplate.refresh(indexName);

        /**------------------不再自动维护version字段-------------------------*/
//        UpdateRequest updateRequest2 = new UpdateRequest();
//        Map<String,Object> map = new HashMap<>();
//        map.put(ElasticsearchScripts.paramValueField,2);
//
//        updateRequest2.script(new Script( ScriptType.INLINE,
//                "painless",
//                /**
//                 * id：是指放在服务端的脚本id
//                 * code: 脚本
//                 * */
//                ElasticsearchScripts.versionUp,
//                map));
//
//        UpdateQuery updateQuery2 = new UpdateQuery();
//        updateQuery2.setIndexName(indexName);
//        updateQuery2.setType(table);
//        updateQuery2.setId(dataId);
//        updateQuery2.setClazz(entityClass);
//        updateQuery2.setUpdateRequest(updateRequest2);

//        List<UpdateQuery> list = new ArrayList();
//        list.add(updateQuery);
//        list.add(updateQuery2);
      //  elasticsearchTemplate.
//        elasticsearchTemplate.bulkUpdate(list);
    }
    /**
     *
     * */
//    @Override
   /* public void updateSomeFields(String indexName,
                                 String table,
                                 String dataId,
                                 Long currentVersion,
                                 Class entityClass,
                                 Map<String,Object> fieldValueMap) throws Exception{

        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.version(currentVersion+1);
//        updateRequest.versionType(VersionType.EXTERNAL);
        updateRequest.doc(contentBuilder(fieldValueMap));

        UpdateQuery updateQuery = new UpdateQuery();
        updateQuery.setIndexName(indexName);
        updateQuery.setType(table);
        updateQuery.setId(dataId);
        updateQuery.setClazz(entityClass);
        updateQuery.setUpdateRequest(updateRequest);

        elasticsearchTemplate.update(updateQuery);
    }*/

    private XContentBuilder contentBuilder(Map<String,Object> fieldValueMap) throws Exception{
        XContentBuilder builder =XContentFactory.jsonBuilder().startObject();
        for(String field:fieldValueMap.keySet()){
            Object val = fieldValueMap.get(field);
            if(     val instanceof List ||
                    val instanceof BaseEntity ||
                    val instanceof Map)//内嵌类型字段
            {
                builder.field(field, JSON.toJSON(fieldValueMap.get(field)));
            } else if (val instanceof Enum)//枚举类型字段
            {
                builder.field(field, ((Enum) val).name());
            } else if (val instanceof Date)//date类型字段
            {
                builder.field(field, Utils.date2DateTimeString((Date) val));
            } else//一般类型字段
            {
                builder.field(field, fieldValueMap.get(field));
            }
        }
        builder.endObject();
        return builder;
    }
    /*private Class<T> resolveReturnedClassFromGenericType() {
        ParameterizedType parameterizedType = resolveReturnedClassFromGenericType(getClass());
        return (Class<T>) parameterizedType.getActualTypeArguments()[0];
    }

    private ParameterizedType resolveReturnedClassFromGenericType(Class<?> clazz) {
        Object genericSuperclass = clazz.getGenericInterfaces();
        if (genericSuperclass instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
            Type rawtype = parameterizedType.getRawType();
            if (SimpleElasticsearchRepository.class.equals(rawtype)) {
                return parameterizedType;
            }
        }
        return resolveReturnedClassFromGenericType(clazz.getSuperclass());
    }

    private boolean isEntityClassSet() {
        return entityClass != null;
    }

    public Class<T> getEntityClass() {
        if (!isEntityClassSet()) {
            try {
                this.entityClass = resolveReturnedClassFromGenericType();
            } catch (Exception e) {
                throw new InvalidDataAccessApiUsageException("Unable to resolve EntityClass. Please use according setter!", e);
            }
        }
        return entityClass;
    }*/

    @Override
    public Map<String, Object> getDataAsMap(String index, String type, String id) {

        GetRequest getRequest = new GetRequest();

        getRequest.index(index)
                  .type(type)
                  .id(id);

        Future<GetResponse> future = elasticsearchTemplate.getClient().get(getRequest);

        GetResponse response = null;
        try {
            response = future.get();
        } catch (InterruptedException e) {
            logger.error("异常：",e);
            return null;
        } catch (ExecutionException e) {
            logger.error("异常：",e);
            return null;
        }
        return response.getSourceAsMap();
    }

    @Override
    /*
     * 传入泛型对象进行更新，只更新对象中不为空的字段的值，为空的字段忽略
     * */
    public boolean updateSomeFieldsOnlyNotNull(T obj){

        boolean rs = false;
        try {

            Class mappedClass = obj.getClass();
            Method m = mappedClass.getMethod("getId");
            if(m == null){
                logger.info("没有getId方法");
                return rs;
            }
            Object idObj = m.invoke(obj);
            if(StringTools.isEmpty(idObj)){
                logger.info("id不能为空");
                return rs;
            }

            Field idField = mappedClass.getDeclaredField("id");
            Transient idTran = idField.getAnnotation(Transient.class);
            if(idTran != null){
                logger.info("id字段不能带Transient注解");
                return rs;
            }
            Field[] fields = mappedClass.getDeclaredFields();
            if(fields.length > 1) {//此处为字段数组大于1，因为起码要有一个id字段
                Map<String, Object> valueMap = new HashMap<String, Object>();
                for (int i = 0; fields != null && i < fields.length; i++) {
                    Field f = fields[i];
                    String fName = f.getName();
                    Transient tran = f.getAnnotation(Transient.class);
                    if (!fName.equals("id")) {
                        if (tran != null) {
                            continue;
                        }

                        f.setAccessible(true);
                        Object valueObj = f.get(obj);
                        if (StringTools.isNotempty(valueObj)) {
                            valueMap.put(fName, valueObj);
                        }
                    }
                }
                if(valueMap.size() > 0){
                    updateSomeFields(idObj.toString().trim(),obj.getClass(),valueMap);
                    rs = true;
                }else {
                    return rs;
                }
            }

        }catch (Exception e){
            logger.info("报错:updateSomeFieldsOnlyNotNull",e);
            rs = false;
        }
        return rs;
    }
}
